package com.gorillalogic.scrummonkey.test {
	import com.gorillalogic.scrummonkey.dms.DmsUtils;
	import com.gorillalogic.scrummonkey.dms.Iteration;
	import com.gorillalogic.scrummonkey.dms.IterationService;
	import com.gorillalogic.scrummonkey.dms.Objective;
	import com.gorillalogic.scrummonkey.dms.ObjectiveService;
	import com.gorillalogic.scrummonkey.dms.Project;
	import com.gorillalogic.scrummonkey.dms.ProjectService;
	import com.gorillalogic.scrummonkey.dms.Release;
	import com.gorillalogic.scrummonkey.dms.ReleaseService;
	
	import mx.collections.ArrayCollection;
	
	import org.flexunit.Assert;

	/**
	 * Tests the following:
	 *   new O -> assigned to P
	 *   new O -> assigned to R
	 *   new O -> assigned to I
	 *   unassign O on I from I
	 *   unassign O on R from R
	 *   unassign O on P from P (causes delete)
	 *   O on R -> child I
	 *   O on P -> child R
	 */
	public class ObjectiveTest extends BaseTest {

		/**
		 * Create hierarchy: P,R,I
		 */
		[Test(async,order=1)]
		public function createObjective():void {
			var p:Project = new Project('proj1','desc-proj1');
			var r:Release = new Release('rel1','desc-rel1');
			var i:Iteration = new Iteration('it1','desc-it1');

			//wireup hierarchy
			p.addRelease(r);
			r.addIteration(i);

			callAndThenOrFault(ProjectService.service.createProject(p), waitForCreateObjective);
		}

		private function waitForCreateObjective(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), verifyCreateObjective);
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), verifyCreateObjective2);
		}

		private function verifyCreateObjective(data:Object, passThroughData:Object):void {
			var project:Project = (data.result as ArrayCollection).getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);
			Assert.assertEquals('Bad project description', 'desc-proj1', project.description);

			//verify release
			Assert.assertEquals('Bad number of releases', 1, project.releases.length);
			var release:Release = project.releases.getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);
			Assert.assertEquals('Bad release description', 'desc-rel1', release.description);
		}

		private function verifyCreateObjective2(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);
			Assert.assertEquals('Bad release description', 'desc-rel1', release.description);

			//verify iteration
			Assert.assertEquals('Bad number of iterations', 1, release.iterations.length);
			var iteration:Iteration = release.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad iteration description', 'desc-it1', iteration.description);
		}


		/**
		 * Create new O under P
		 */
		[Test(async,order=2)]
		public function newObjectiveA1():void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), newObjectiveA2);
		}

		private function newObjectiveA2(data:Object, passThroughData:Object):void {
			var project:Project = (data.result as ArrayCollection).getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);

			var objective:Objective = new Objective('objP','desc-objP');

			//Normally, we can just call project.addObjective() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					project.addObjective(objective);
				}), newObjectiveA3);
		}

		private function newObjectiveA3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), newObjectiveA4);
		}

		private function newObjectiveA4(data:Object, passThroughData:Object):void {
			var project:Project = (data.result as ArrayCollection).getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);

			Assert.assertEquals('Bad number of objectives', 1, project.objectives.length);
			var objective:Objective = project.objectives.getItemAt(0) as Objective;
			Assert.assertTrue('Bad objective name', 'objP', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertNull("Bad objective's release", objective.release);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Create new O under R (and thus P)
		 */
		[Test(async,order=3)]
		public function newObjectiveB1():void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), newObjectiveB2);
		}

		private function newObjectiveB2(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			var objective:Objective = new Objective('objR','desc-objR');

			//Normally, we can just call release.addObjective() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					release.addObjective(objective);
				}), newObjectiveB3);
		}

		private function newObjectiveB3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), newObjectiveB4);
		}

		private function newObjectiveB4(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of objectives', 1, release.objectives.length);
			var objective:Objective = release.objectives.getItemAt(0) as Objective;
			Assert.assertTrue('Bad objective name', 'objR', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Create new O under I (and thus both P & R)
		 */
		[Test(async,order=4)]
		public function newObjectiveC1():void {
			callAndThenOrFault(IterationService.service.getByName('it1'), newObjectiveC2);
		}

		private function newObjectiveC2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);

			var objective:Objective = new Objective('objI','desc-objI');

			//Normally, we can just call iteration.addObjective() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					iteration.addObjective(objective);
				}), newObjectiveC3);
		}

		private function newObjectiveC3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(IterationService.service.getByName('it1'), newObjectiveC4);
		}

		private function newObjectiveC4(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);

			Assert.assertEquals('Bad number of objectives', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertTrue('Bad objective name', 'objI', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);
		}


		/**
		 * Unassign O from I and verify it is still on both P & R
		 */
		[Test(async,order=5)]
		public function unassignObjectiveA1():void {
			callAndThenOrFault(ObjectiveService.service.getByName('objI'), unassignObjectiveA2);
		}

		private function unassignObjectiveA2(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objI', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);

			//Normally, we can just call objective.unassign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					objective.unassign(objective.iteration);
				}), unassignObjectiveA3);
		}

		private function unassignObjectiveA3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ObjectiveService.service.getByName('objI'), unassignObjectiveA4);
		}

		private function unassignObjectiveA4(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objI', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Unassign O from R and verify it is still on P
		 */
		[Test(async,order=6)]
		public function unassignObjectiveB1():void {
			callAndThenOrFault(ObjectiveService.service.getByName('objR'), unassignObjectiveB2);
		}

		private function unassignObjectiveB2(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objR', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			//Normally, we can just call objective.unassign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					objective.unassign(objective.release);
				}), unassignObjectiveB3);
		}

		private function unassignObjectiveB3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ObjectiveService.service.getByName('objR'), unassignObjectiveB4);
		}

		private function unassignObjectiveB4(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objR', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertNull("Bad objective's release", objective.release);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Unassign O from P (should cause O to be deleted)
		 */
		[Test(async,order=7)]
		public function unassignObjectiveC1():void {
			callAndThenOrFault(ObjectiveService.service.getByName('objP'), unassignObjectiveC2);
		}

		private function unassignObjectiveC2(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objP', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertNull("Bad objective's release", objective.release);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			//Normally, we can just call objective.unassign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					objective.unassign(objective.project);
				}), unassignObjectiveC3);
		}

		private function unassignObjectiveC3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ObjectiveService.service.getByName('objP'), unassignObjectiveC4);
		}

		private function unassignObjectiveC4(data:Object, passThroughData:Object):void {
			var objectives:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Failed to delete objective upon unassignment from project', 0, objectives.length);
		}


		/**
		 * Reassign O on R to child I
		 */
		[Test(async,order=8)]
		public function reassignObjectiveA1():void {
			callAndThenOrFault(ObjectiveService.service.getByName('objI'), reassignObjectiveA2);
		}

		private function reassignObjectiveA2(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objI', objective.name);
			Assert.assertEquals("Bad objective's project name", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release name", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			callAndThenOrFault(IterationService.service.getByName('it1'), reassignObjectiveA3, null, 5000, objective);
		}

		private function reassignObjectiveA3(data:Object, passThroughData:Object):void {
			var objective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'objI', objective.name);

			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals("Bad iteration's release name", 'rel1', iteration.release.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					objective.assign(iteration);
				}), reassignObjectiveA4);
		}

		private function reassignObjectiveA4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ObjectiveService.service.getByName('objI'), reassignObjectiveA5);
		}

		private function reassignObjectiveA5(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objI', objective.name);
			Assert.assertEquals("Bad objective's project name", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release name", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration name", 'it1', objective.iteration.name);
		}


		/**
		 * Reassign O on P to child R
		 */
		[Test(async,order=9)]
		public function reassignObjectiveB1():void {
			callAndThenOrFault(ObjectiveService.service.getByName('objR'), reassignObjectiveB2);
		}

		private function reassignObjectiveB2(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objR', objective.name);
			Assert.assertEquals("Bad objective's project name", 'proj1', objective.project.name);
			Assert.assertNull("Bad objective's release", objective.release);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			callAndThenOrFault(ReleaseService.service.getByName('rel1'), reassignObjectiveB3, null, 5000, objective);
		}

		private function reassignObjectiveB3(data:Object, passThroughData:Object):void {
			var objective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'objR', objective.name);

			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);
			Assert.assertEquals("Bad release's project name", 'proj1', release.project.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ProjectService.service,
				function ():void {
					objective.assign(release);
				}), reassignObjectiveB4);
		}

		private function reassignObjectiveB4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ObjectiveService.service.getByName('objR'), reassignObjectiveB5);
		}

		private function reassignObjectiveB5(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'objR', objective.name);
			Assert.assertEquals("Bad objective's project name", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release name", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Cleanup everything
		 */
		[Test(async,order=10)]
		public function cleanup():void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), waitForQueryProjectCleanup);
		}

		private function waitForQueryProjectCleanup(data:Object, passThroughData:Object):void {
			var project:Project = (data.result as ArrayCollection).getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);

			callAndThenOrFault(ProjectService.service.deleteProject(project), waitForCleanup);
		}

		private function waitForCleanup(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), verifyCleanup);
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), verifyCleanup);
			callAndThenOrFault(IterationService.service.getByName('it1'), verifyCleanup);
			callAndThenOrFault(ObjectiveService.service.getByName('objP'), verifyCleanup);
			callAndThenOrFault(ObjectiveService.service.getByName('objR'), verifyCleanup);
			callAndThenOrFault(ObjectiveService.service.getByName('objI'), verifyCleanup);
		}

		private function verifyCleanup(data:Object, passThroughData:Object):void {
			var entities:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Cleanup failed', 0, entities.length);
		}
	}
}